#include "mp4_ms.h"

namespace MP4 {

  static char c2hex(int c) {
    if (c >= '0' && c <= '9') return c - '0';
    if (c >= 'a' && c <= 'f') return c - 'a' + 10;
    if (c >= 'A' && c <= 'F') return c - 'A' + 10;
    return 0;
  }


  SDTP::SDTP() {
    memcpy(data + 4, "sdtp", 4);
  }

  void SDTP::setVersion(uint32_t newVersion) {
    setInt8(newVersion, 0);
  }

  uint32_t SDTP::getVersion() {
    return getInt8(0);
  }

  void SDTP::setValue(uint32_t newValue, size_t index) {
    setInt8(newValue, index);
  }

  uint32_t SDTP::getValue(size_t index) {
    return getInt8(index);
  }

  std::string SDTP::toPrettyString(uint32_t indent) {
    std::stringstream r;
    r << std::string(indent, ' ') << "[sdtp] Sample Dependancy Type (" << boxedSize() << ")" << std::endl;
    r << std::string(indent + 1, ' ') << "Samples: " << (boxedSize() - 12) << std::endl;
    for (size_t i = 1; i <= boxedSize() - 12; ++i) {
      uint32_t val = getValue(i + 3);
      r << std::string(indent + 2, ' ') << "[" << i << "] = ";
      switch (val & 3) {
        case 0:
          r << "               ";
          break;
        case 1:
          r << "Redundant,     ";
          break;
        case 2:
          r << "Not redundant, ";
          break;
        case 3:
          r << "Error,         ";
          break;
      }
      switch (val & 12) {
        case 0:
          r << "                ";
          break;
        case 4:
          r << "Not disposable, ";
          break;
        case 8:
          r << "Disposable,     ";
          break;
        case 12:
          r << "Error,          ";
          break;
      }
      switch (val & 48) {
        case 0:
          r << "            ";
          break;
        case 16:
          r << "IFrame,     ";
          break;
        case 32:
          r << "Not IFrame, ";
          break;
        case 48:
          r << "Error,      ";
          break;
      }
      r << "(" << val << ")" << std::endl;
    }
    return r.str();
  }

  UUID::UUID() {
    memcpy(data + 4, "uuid", 4);
    setInt64(0, 0);
    setInt64(0, 8);
  }

  std::string UUID::getUUID() {
    std::stringstream r;
    r << std::hex;
    for (int i = 0; i < 16; ++i) {
      if (i == 4 || i == 6 || i == 8 || i == 10) {
        r << "-";
      }
      r << std::setfill('0') << std::setw(2) << std::right << (int)(data[8 + i]);
    }
    return r.str();
  }

  void UUID::setUUID(const std::string & uuid_string) {
    //reset UUID to zero
    for (int i = 0; i < 4; ++i) {
      ((uint32_t *)(data + 8))[i] = 0;
    }
    //set the UUID from the string, char by char
    int i = 0;
    for (size_t j = 0; j < uuid_string.size(); ++j) {
      if (uuid_string[j] == '-') {
        continue;
      }
      data[8 + i / 2] |= (c2hex(uuid_string[j]) << ((~i & 1) << 2));
      ++i;
    }
  }

  void UUID::setUUID(const char * raw_uuid) {
    memcpy(data + 8, raw_uuid, 16);
  }

  std::string UUID::toPrettyString(uint32_t indent) {
    std::string UUID = getUUID();
    if (UUID == "d4807ef2-ca39-4695-8e54-26cb9e46a79f") {
      return ((UUID_TrackFragmentReference *)this)->toPrettyString(indent);
    }
    if (UUID == "6d1d9b05-42d5-44e6-80e2-141daff757b2") {
      return ((UUID_TFXD *)this)->toPrettyString(indent);
    }
    std::stringstream r;
    r << std::string(indent, ' ') << "[uuid] Extension box (" << boxedSize() << ")" << std::endl;
    r << std::string(indent + 1, ' ') << "UUID: " << UUID << std::endl;
    r << std::string(indent + 1, ' ') << "Unknown UUID - ignoring contents." << std::endl;
    return r.str();
  }

  UUID_TrackFragmentReference::UUID_TrackFragmentReference() {
    setUUID((std::string)"d4807ef2-ca39-4695-8e54-26cb9e46a79f");
  }

  void UUID_TrackFragmentReference::setVersion(uint32_t newVersion) {
    setInt8(newVersion, 16);
  }

  uint32_t UUID_TrackFragmentReference::getVersion() {
    return getInt8(16);
  }

  void UUID_TrackFragmentReference::setFlags(uint32_t newFlags) {
    setInt24(newFlags, 17);
  }

  uint32_t UUID_TrackFragmentReference::getFlags() {
    return getInt24(17);
  }

  void UUID_TrackFragmentReference::setFragmentCount(uint32_t newCount) {
    setInt8(newCount, 20);
  }

  uint32_t UUID_TrackFragmentReference::getFragmentCount() {
    return getInt8(20);
  }

  void UUID_TrackFragmentReference::setTime(size_t num, uint64_t newTime) {
    if (getVersion() == 0) {
      setInt32(newTime, 21 + (num * 8));
    } else {
      setInt64(newTime, 21 + (num * 16));
    }
  }

  uint64_t UUID_TrackFragmentReference::getTime(size_t num) {
    if (getVersion() == 0) {
      return getInt32(21 + (num * 8));
    } else {
      return getInt64(21 + (num * 16));
    }
  }

  void UUID_TrackFragmentReference::setDuration(size_t num, uint64_t newDuration) {
    if (getVersion() == 0) {
      setInt32(newDuration, 21 + (num * 8) + 4);
    } else {
      setInt64(newDuration, 21 + (num * 16) + 8);
    }
  }

  uint64_t UUID_TrackFragmentReference::getDuration(size_t num) {
    if (getVersion() == 0) {
      return getInt32(21 + (num * 8) + 4);
    } else {
      return getInt64(21 + (num * 16) + 8);
    }
  }

  std::string UUID_TrackFragmentReference::toPrettyString(uint32_t indent) {
    std::stringstream r;
    r << std::string(indent, ' ') << "[d4807ef2-ca39-4695-8e54-26cb9e46a79f] Track Fragment Reference (" << boxedSize() << ")" << std::endl;
    r << std::string(indent + 1, ' ') << "Version: " << getVersion() << std::endl;
    r << std::string(indent + 1, ' ') << "Fragments: " << getFragmentCount() << std::endl;
    int j = getFragmentCount();
    for (int i = 0; i < j; ++i) {
      r << std::string(indent + 2, ' ') << "[" << i << "] Time = " << getTime(i) << ", Duration = " << getDuration(i) << std::endl;
    }
    return r.str();
  }

  UUID_TFXD::UUID_TFXD() {
    setUUID((std::string)"6d1d9b05-42d5-44e6-80e2-141daff757b2");
    setVersion(0);
    setFlags(0);
  }

  void UUID_TFXD::setVersion(uint32_t newVersion) {
    setInt8(newVersion, 16);
  }

  uint32_t UUID_TFXD::getVersion() {
    return getInt8(16);
  }

  void UUID_TFXD::setFlags(uint32_t newFlags) {
    setInt24(newFlags, 17);
  }

  uint32_t UUID_TFXD::getFlags() {
    return getInt24(17);
  }

  void UUID_TFXD::setTime(uint64_t newTime) {
    if (getVersion() == 0) {
      setInt32(newTime, 20);
    } else {
      setInt64(newTime, 20);
    }
  }

  uint64_t UUID_TFXD::getTime() {
    if (getVersion() == 0) {
      return getInt32(20);
    } else {
      return getInt64(20);
    }
  }

  void UUID_TFXD::setDuration(uint64_t newDuration) {
    if (getVersion() == 0) {
      setInt32(newDuration, 24);
    } else {
      setInt64(newDuration, 28);
    }
  }

  uint64_t UUID_TFXD::getDuration() {
    if (getVersion() == 0) {
      return getInt32(24);
    } else {
      return getInt64(28);
    }
  }

  std::string UUID_TFXD::toPrettyString(uint32_t indent) {
    std::stringstream r;
    setUUID((std::string)"6d1d9b05-42d5-44e6-80e2-141daff757b2");
    r << std::string(indent, ' ') << "[6d1d9b05-42d5-44e6-80e2-141daff757b2] TFXD Box (" << boxedSize() << ")" << std::endl;
    r << std::string(indent + 1, ' ') << "Version: " << getVersion() << std::endl;
    r << std::string(indent + 1, ' ') << "Time = " << getTime() << std::endl;
    r << std::string(indent + 1, ' ') << "Duration = " << getDuration() << std::endl;
    return r.str();
  }
}
